<template>
  <div class="editor-container">
    <div class="left-board">
      <div class="logo-wrapper">
        <div class="logo">表单配置</div>
      </div>
      <el-scrollbar class="left-scrollbar">
        <div class="components-list">
          <div v-for="(item, listIndex) in leftComponents" :key="listIndex">
            <div class="components-title">
              <svg-icon icon-class="component" />
              {{ item.title }}
            </div>
            <VueDraggable
              class="components-draggable"
              v-model="item.list"
              :group="{ name: 'componentsGroup', pull: 'clone', put: false }"
              :clone="cloneComponent"
              draggable=".components-item"
              @add="onAdd"
            >
              <div
                v-for="(element, index) in item.list"
                :key="index"
                class="components-item"
              >
                <div class="components-body">
                  <svg-icon :icon-class="element.__config__.tagIcon" />
                  {{ element.__config__.label }}
                </div>
              </div>
            </VueDraggable>
          </div>
        </div>
      </el-scrollbar>
    </div>

    <div class="center-board">
      <div class="action-bar">
        <el-button icon="el-icon-video-play" type="text" @click="run">
          运行
        </el-button>
        <el-button icon="el-icon-view" type="text" @click="showJson">
          查看json
        </el-button>
        <el-button icon="el-icon-download" type="text" @click="download">
          导出vue文件
        </el-button>
        <el-button
          class="copy-btn-main"
          icon="el-icon-document-copy"
          type="text"
          @click="showCopy"
        >
          复制代码
        </el-button>
        <el-button
          class="delete-btn"
          icon="el-icon-delete"
          type="text"
          @click="empty"
        >
          清空
        </el-button>
      </div>
      <el-scrollbar class="center-scrollbar">
        <el-row class="center-board-row" :gutter="formConf.gutter">
          <el-form
            :size="formConf.size"
            :label-position="formConf.labelPosition"
            :disabled="formConf.disabled"
            :label-width="formConf.labelWidth + 'px'"
          >
            <VueDraggable
              class="drawing-board"
              v-model="drawingList"
              :animation="340"
              group="componentsGroup"
            >
              <draggable-item
                v-for="(item, index) in drawingList"
                :key="item.renderKey"
                :drawing-list="drawingList"
                :current-item="item"
                :index="index"
                :active-id="activeId"
                :form-conf="formConf"
                @active-item="activeFormItem"
                @copy-item="drawingItemCopy"
                @delete-item="drawingItemDelete"
              />
            </VueDraggable>
            <div v-show="!drawingList.length" class="empty-info">
              从左侧拖入或点选组件进行表单设计
            </div>
          </el-form>
        </el-row>
      </el-scrollbar>
    </div>

    <right-panel
      :active-data="activeData"
      :form-conf="formConf"
      :show-field="!!drawingList.length"
      @tag-change="tagChange"
      @fetch-data="fetchData"
    />
  </div>
  <div>
    <form-drawer
      v-model:visible="drawerVisible"
      :form-data="formData"
      size="100%"
      :generate-conf="generateConf"
    />
    <json-drawer
      size="60%"
      v-model:visible="jsonDrawerVisible"
      :json-str="JSON.stringify(formData)"
      @refresh="refreshJson"
    />
    <code-type-dialog
      v-model:visible="dialogVisible"
      title="选择生成类型"
      :show-file-name="showFileName"
      @confirm="generate"
    />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, watch, computed } from "vue";
import { useClipboard } from "@vueuse/core";
import { VueDraggable } from "vue-draggable-plus";
import { saveAs } from "file-saver";
import { debounce } from "throttle-debounce";
import { html_beautify } from "js-beautify";
import {
  exportDefault,
  beautifierConf,
  isNumberStr,
  titleCase,
  deepClone,
  isObjectObject,
} from "./utils/common";
import {
  getDrawingList,
  saveDrawingList,
  getIdGlobal,
  saveIdGlobal,
  getFormConf,
} from "./utils/db";
import { makeUpHtml, vueTemplate, vueScript, cssStyle } from "./settings/html";
import { makeUpJs } from "./settings/js";
import { makeUpCss } from "./settings/css";
import drawingDefalut from "./settings/drawingDefalut";
import {
  inputComponents,
  selectComponents,
  layoutComponents,
  formConf,
} from "./settings/config";
import draggableItem from "./components/DraggableItem";
import rightPanel from "./components/RightPanel";
import formDrawer from "./components/FormDrawer";
import jsonDrawer from "./components/JsonDrawer";
import codeTypeDialog from "./components/CodeTypeDialog";

const { copy, isSupported } = useClipboard();
const idGlobal = ref(100);
const tempActiveData = ref(null);
const drawingList = ref([]);
const activeId = ref(null);
const oldActiveId = ref({});
const drawerVisible = ref(false);
const formData = ref({});
const dialogVisible = ref(false);
const jsonDrawerVisible = ref(false);
const generateConf = ref(null);
const showFileName = ref(false);
const activeData = ref({});
const leftComponents = ref([
  { title: "输入型组件", list: inputComponents },
  { title: "选择型组件", list: selectComponents },
  { title: "布局型组件", list: layoutComponents },
]);
const operationType = ref("");

watch(activeId, (val) => {
  oldActiveId.value = val;
});

watch(
  drawingList,
  (val) => {
    saveDrawingListDebounce(val);
    if (val.length === 0) {
      idGlobal.value = 100;
    }
  },
  { deep: true }
);

watch(idGlobal, (val) => {
  saveIdGlobalDebounce(val);
});

watch(
  () => activeData?.value,
  (val, oldVal) => {
    if (
      activeData.value.placeholder === undefined ||
      !activeData.value.__config__.tag ||
      oldActiveId.value !== activeId.value
    ) {
      return;
    }
    activeData.value.placeholder =
      activeData.value.placeholder.replace(oldVal, "") + val;
  },
  {
    deep: true,
  }
);

onMounted(() => {
  const drawingListInDB = getDrawingList();
  if (Array.isArray(drawingListInDB) && drawingListInDB.length > 0) {
    drawingList.value = drawingListInDB;
  } else {
    drawingList.value = drawingDefalut;
  }
  if (drawingList.value.length > 0) {
    activeFormItem(drawingList.value[0]);
  }
});

const saveDrawingListDebounce = debounce(340, () =>
  saveDrawingList(drawingList.value)
);

const saveIdGlobalDebounce = debounce(340, () => saveIdGlobal(idGlobal.value));

const setObjectValueReduce = (obj, strKeys, data) => {
  const arr = strKeys.split(".");
  arr.reduce((pre, item, i) => {
    if (arr.length === i + 1) {
      pre[item] = data;
    } else if (!isObjectObject(pre[item])) {
      pre[item] = {};
    }
    return pre[item];
  }, obj);
};
const setRespData = (component, resp) => {
  const { dataPath, renderKey, dataConsumer } = component.__config__;
  if (!dataPath || !dataConsumer) return;
  const respData = dataPath.split(".").reduce((pre, item) => pre[item], resp);

  // 将请求回来的数据，赋值到指定属性。
  // 以el-tabel为例，根据Element文档，应该将数据赋值给el-tabel的data属性，所以dataConsumer的值应为'data';
  // 此时赋值代码可写成 component[dataConsumer] = respData；
  // 但为支持更深层级的赋值（如：dataConsumer的值为'options.data'）,使用setObjectValueReduce
  setObjectValueReduce(component, dataConsumer, respData);
  const i = drawingList.value.findIndex(
    (item) => item.__config__.renderKey === renderKey
  );
  if (i > -1) {
    drawingList.value[i] = component;
  }
};
const fetchData = (component) => {
  const { dataType, method, url } = component.__config__;
  if (dataType === "dynamic" && method && url) {
    setLoading(component, true);
    $axios({
      method,
      url,
    }).then((resp) => {
      setLoading(component, false);
      setRespData(component, resp.data);
    });
  }
};
const setLoading = (component, val) => {
  const { directives } = component;
  if (Array.isArray(directives)) {
    const t = directives.find((d) => d.name === "loading");
    if (t) t.value = val;
  }
};
const activeFormItem = (currentItem) => {
  activeData.value = currentItem;
  activeId.value = currentItem.__config__.formId;
};
const onAdd = (event) => {
  console.log("=================", drawingList.value);

  if (event.from !== event.to) {
    fetchData(tempActiveData.value);
    activeData.value = tempActiveData.value;
    activeId.value = idGlobal.value;
  }
};

const addComponent = (item) => {
  const clone = cloneComponent(item);
  fetchData(clone);
  drawingList.value.push(clone);
  activeFormItem(clone);
};

const cloneComponent = (origin) => {
  console.log("origin", origin);
  const clone = deepClone(origin);
  const config = clone.__config__;
  config.span = formConf.span; // 生成代码时，会根据span做精简判断
  createIdAndKey(clone);
  clone.placeholder !== undefined && (clone.placeholder += config.label);
  tempActiveData.value = clone;
  return tempActiveData;
};
const createIdAndKey = (item) => {
  const config = item.__config__;
  config.formId = ++idGlobal.value;
  config.renderKey = `${config.formId}${+new Date()}`; // 改变renderKey后可以实现强制更新组件
  if (config.layout === "colFormItem") {
    item.__vModel__ = `field${idGlobal.value}`;
  } else if (config.layout === "rowFormItem") {
    config.componentName = `row${idGlobal.value}`;
    !Array.isArray(config.children) && (config.children = []);
    delete config.label; // rowFormItem无需配置label属性
  }
  if (Array.isArray(config.children)) {
    config.children = config.children.map((childItem) =>
      createIdAndKey(childItem)
    );
  }
  return item;
};
const AssembleFormData = () => {
  formData.value = {
    fields: deepClone(drawingList.value),
    ...formConf,
  };
};
const generate = (data) => {
  const funcName = `exec${titleCase(operationType.value)}`;
  const func = globals[funcName];
  generateConf.value = data;
  func && func(data);
};
const execRun = (data) => {
  AssembleFormData();
  drawerVisible.value = true;
};
const execDownload = (data) => {
  const codeStr = generateCode();
  const blob = new Blob([codeStr], { type: "text/plain;charset=utf-8" });
  saveAs(blob, data.fileName);
};
const execCopy = (data) => {
  if (!isSupported) {
    ElMessageBox.message({
      showClose: true,
      message: "浏览器不支持复制",
      type: "warning",
    });
    return;
  }
  const codeStr = generateCode();
  copy(codeStr);
  ElMessageBox.message({
    showClose: true,
    message: "代码已复制到剪切板，可粘贴。",
    type: "success",
  });
};
const globals = {
  execRun,
  execDownload,
  execCopy,
};
const empty = () => {
  ElMessageBox.confirm("确定要清空所有组件吗？", "提示", {
    type: "warning",
  }).then(() => {
    drawingList.value = [];
    idGlobal.value = 100;
  });
};
const drawingItemCopy = (item, list) => {
  let clone = deepClone(item);
  clone = createIdAndKey(clone);
  list.push(clone);
  activeFormItem(clone);
};
const drawingItemDelete = (index, list) => {
  list.splice(index, 1);
  nextTick(() => {
    const len = drawingList.value.length;
    if (len) {
      activeFormItem(drawingList.value[len - 1]);
    }
  });
};
const generateCode = () => {
  const { type } = generateConf.value;
  AssembleFormData();
  const script = vueScript(makeUpJs(formData.value, type));
  const html = vueTemplate(makeUpHtml(formData.value, type));
  const css = cssStyle(makeUpCss(formData.value));
  return html_beautify(html + script + css);
};
const showJson = () => {
  AssembleFormData();
  jsonDrawerVisible.value = true;
};
const download = () => {
  dialogVisiblev = true;
  showFileName.value = true;
  operationType.value = "download";
};
const run = () => {
  dialogVisible.value = true;
  showFileName.value = false;
  operationType.value = "run";
};
const showCopy = () => {
  dialogVisible.value = true;
  showFileName.value = false;
  operationType.value = "copy";
};
const tagChange = (newTag) => {
  newTag = cloneComponent(newTag);
  const config = newTag.__config__;
  newTag.__vModel__ = activeData.value.__vModel__;
  config.formId = activeId.value;
  config.span = activeData.value.__config__.span;
  activeData.value.__config__.tag = config.tag;
  activeData.value.__config__.tagIcon = config.tagIcon;
  activeData.value.__config__.document = config.document;
  if (
    typeof activeData.value.__config__.defaultValue ===
    typeof config.defaultValue
  ) {
    config.defaultValue = activeData.value.__config__.defaultValue;
  }
  Object.keys(newTag).forEach((key) => {
    if (activeData.value[key] !== undefined) {
      newTag[key] = activeData.value[key];
    }
  });
  activeData.value = newTag;
  updateDrawingList(newTag, drawingList.value);
};
const updateDrawingList = (newTag, list) => {
  const index = list.findIndex(
    (item) => item.__config__.formId === activeId.value
  );
  if (index > -1) {
    list.splice(index, 1, newTag);
  } else {
    list.forEach((item) => {
      if (Array.isArray(item.__config__.children))
        updateDrawingList(newTag, item.__config__.children);
    });
  }
};
const refreshJson = (data) => {
  drawingList.value = deepClone(data.fields);
  delete data.fields;
  formConf.value = data;
};

onUnmounted(() => {
  saveDrawingListDebounce.cancel();
  saveIdGlobalDebounce.cancel();
});
</script>

<style lang="scss">
@use "./index.scss";
</style>
